package JavaEE.ManyThread4;
import java.util.*;
public class Demo3 {
    // 这种格式会引发"内存可见性"(编译器优化)问题
    // public static int flg = 0;
    public static volatile int flg = 0;
    // -使用volatile关键字-
    // 1. 保证变量的可见性
    // 2. 禁止指令重排序
    // (指令重排序):仍然是"编译器优化",比如我要买菜,修改买菜的顺序缩短路径,以提高效率~
    // 3. 轻量级解决方案
    public static void main(String[] args) throws InterruptedException{
        Scanner in = new Scanner(System.in);
        Thread t1 = new Thread(() -> {
            // 当t2运行并为flg赋值后,期望的情况是t1结束,但如果没有加volatile的话,
            // 但实际上并没有结束,这是因为"内存可见性" -> flg == 0 需要从内存中获取并比较,耗时多
            // 编译器发现while内部没有对flg进行影响的操作,所以自动改成 while(true) (改为寄存器读取,而非内存)
            while(flg == 0){
                //啥也不做...
                //如果sleep(1),就不会触发优化,因为"从内存获取flg值"的耗时和sleep(1)比,就是弟中弟~
            }
            System.out.println("线程t1结束");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("请为flg赋值:");
            flg = in.nextInt();
            System.out.println("线程t2结束");
        });
        t1.start();
        t2.start();
    }
}
// 引发"内存可见性"问题的原因:
// 首先 要知道:(容量): 外存(硬盘) > 内存 > 缓存(L3>L2>L1) > 寄存器
//           (速度): 外存(硬盘) < 内存 < 缓存(L3<L2<L1) < 寄存器
// 而此时flg属于在"内存"中,在t1中的while(flg == 0),每次判定需要从"内存"中获取flg的值
// 这是比较慢的,而这种操作进行多次后,编译器认为没有影响flg的操作,则"优化"成了"从寄存器/缓存中获取flg"
// 而后续在t2中修改flg的值后,修改的位置是内存,并不会对"t1的寄存器/缓存"位置的flg进行修改,则造成死循环
// 而想要避免这种情况,就需要使用volatile进行修饰(使得线程之间变量修改保证可见性)
// volatile的作用:
// 1. 禁止指令重排序(内存屏障)
// 2. 保证写入后立即刷新到主内存,读取时直接从主内存获取(而不是缓存).


